package struct_config

import (
    "errors"
    "log"
    "reflect"
    "strconv"
    "strings"
)

func GetSturctFields(v interface{}) (fields []string, err error) {
    typ := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        typ = typ.Elem()
    }

    if typ.Kind() != reflect.Struct {
        err = ErrNotStruct
        return
    }

    for i := 0; i < typ.NumField(); i++ {
        fields = append(fields, typ.Field(i).Name)
    }
    err = nil
    return
}

func StructArrayFromString(names, values []string, elem interface{}) (result interface{}, err error) {
    if len(names) != len(values) {
        return nil, ErrNotEqualCount
    }
    elem_type := reflect.TypeOf(elem)
    if elem_type.Kind() == reflect.Ptr {
        elem_type = reflect.ValueOf(elem).Elem().Type()
    }
    if elem_type.Kind() != reflect.Struct {
        return nil, ErrNotStruct
    }

    max_index := 0
    store := make(map[int]reflect.Value)
    for i, name := range names {
        name_frags := strings.Split(name, ":")
        if len(name_frags) != 3 {

            continue
        }
        idx, err := strconv.Atoi(name_frags[1])
        if err != nil {

            continue
        }
        field_name := name_frags[2]
        value_str := values[i]

        struct_value, ok := store[idx]
        if !ok {

            struct_value = reflect.New(elem_type).Elem()
            store[idx] = struct_value
        }
        if idx > max_index {
            max_index = idx
        }

        field_value := struct_value.FieldByName(field_name)
        z := reflect.Value{}
        if field_value == z {

            continue
        }
        if err := StringToReflectValue(value_str, field_value); err != nil {

            continue
        }

    }

    lst := reflect.MakeSlice(reflect.SliceOf(elem_type), max_index+1, max_index+1)
    for i := 0; i <= max_index; i++ {
        v, ok := store[i]
        if !ok {
            v = reflect.New(elem_type).Elem()
        }
        lst.Index(i).Set(v)
    }
    return lst.Interface(), nil
}

type ConfigStoreQuery func(group string, names []string) ([]string, error)

type ConfigStoreUpdate func(group string, names, values []string) error

func ConfigToStruct(store_query ConfigStoreQuery, group string, ptr_struct interface{}) error {
    val := reflect.ValueOf(ptr_struct)
    if val.Kind() != reflect.Ptr {
        return errors.New("not a ptr")
    }
    if val.Elem().Kind() != reflect.Struct {
        return errors.New("not a struct")
    }
    val = val.Elem()

    fields, err := GetSturctFields(ptr_struct)
    if err != nil {
        return err
    }

    values, err := store_query(group, fields)
    if err != nil {
        return err
    }

    for i := 0; i < len(fields); i++ {
        str_name := fields[i]
        str_value := values[i]

        field := val.FieldByName(str_name)
        if err := StringToReflectValue(str_value, field); err != nil {
            log.Println("set field", str_name, str_value, "failed:", err)
        }
    }
    return nil
}

func StructToConfig(store_update ConfigStoreUpdate, group string, ptr_struct interface{}) error {
    val := reflect.ValueOf(ptr_struct)
    if val.Kind() == reflect.Ptr {
        val = val.Elem()
    }
    if val.Kind() != reflect.Struct {
        return errors.New("not a struct")
    }

    fields, err := GetSturctFields(ptr_struct)
    if err != nil {
        return err
    }
    var names, values []string
    for _, f := range fields {
        field := val.FieldByName(f)
        vs, err := ReflectValueToString(field)
        if err != nil {
            log.Println(field, err)
            continue
        }
        names = append(names, f)
        values = append(values, vs)
    }

    return store_update(group, names, values)
}

func FieldNamesAndTagValuesFromStruct(v interface{}, tagKey string) (fields, tagValues []string, err error) {
    typ := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        typ = typ.Elem()
    }

    if typ.Kind() != reflect.Struct {
        err = errors.New("not a struct")
        return
    }

    for i := 0; i < typ.NumField(); i++ {
        fields = append(fields, typ.Field(i).Name)
        tagValues = append(tagValues, typ.Field(i).Tag.Get(tagKey))
    }
    err = nil
    return
}

func FieldNamesFromStruct(v interface{}) (fields []string, err error) {
    typ := reflect.TypeOf(v)
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Ptr {
        typ = typ.Elem()
    }

    if typ.Kind() != reflect.Struct {
        err = errors.New("not a struct")
        return
    }

    for i := 0; i < typ.NumField(); i++ {
        fields = append(fields, typ.Field(i).Name)
    }
    err = nil
    return
}
